/*
 * ItemDiff.java
 *
 * Created on August 6, 2004, 5:34 PM
 */

package org.infoset.xml.util;

import java.io.*;
import java.util.*;
import org.infoset.xml.*;

/**
 *
 *
 * @author  R. Alexander Milowski
 */
public class ItemDiff implements ItemDestination {

   PrintWriter out;
   Document doc;
   
   Element current;
   Child currentChild;
   List childrenContext;
   int flushLevel;
   boolean difference;
   boolean ignoreWhitespace;
   
   /** Creates a new instance of ItemDiff */
   public ItemDiff(Document d,PrintWriter out) {
      this.doc = d;
      this.current = null;
      this.currentChild = null;
      this.out = out;
      this.childrenContext = new ArrayList();
      this.flushLevel = 0;
      this.difference = false;
      this.ignoreWhitespace = false;
   }
   
   public ItemDiff(Element e,PrintWriter out) {
      this.doc = null;
      this.current = e;
      this.currentChild = e;
      this.out = out;
      this.childrenContext = new ArrayList();
      this.flushLevel = 0;
      this.difference = false;
      this.ignoreWhitespace = false;
   }
   
   public boolean isDifferent() {
      return difference;
   }
   
   public void setIgnoreWhitespace(boolean flag) {
      this.ignoreWhitespace = flag;
   }
   
   public void send(Item item) throws XMLException {
      if (flushLevel>0) {
         switch (item.getType()) {
            case ElementItem:
               flushLevel++;
               break;
            case ElementEndItem:
               flushLevel--;
         }
         if (item instanceof Child) {
            logChild(false,(Child)item);
         }
         return;
      }
      
      switch (item.getType()) {
         case DocumentItem:
            current = null;
            break;
         case DocumentEndItem:
            break;
         case ElementEndItem:
            if (currentChild!=null) {
               logSiblings(true,currentChild);
            }
            Parent p = current.getParent();
            if (p instanceof Document) {
               current = null;
               currentChild = null;
            } else {
               current = (Element)p;
               currentChild = (Child)childrenContext.remove(childrenContext.size()-1);
            }
            break;
         case ElementItem: {
            Element e = (Element)item;
            if (current==null) {
               // The document element
               current = doc.getDocumentElement();
               if (!current.getName().equals(e.getName())) {
                  logMessage(false,e,"Document element "+e.getName()+" does not match.");
                  difference = true;
               }
               currentChild = current.get(0);
            } else if (currentChild!=null) {
               while (currentChild!=null && currentChild.getType()!=Item.ItemType.ElementItem) {
                  if (ignoreWhitespace && currentChild.getType()==Item.ItemType.CharactersItem) {
                     Characters cchild = (Characters)currentChild;
                     if (!allWhitespace(cchild.getText())) {
                        logChild(true,currentChild);
                        difference = true;
                     }
                  } else {
                     difference = true;
                     logChild(true,currentChild);
                  }
                  currentChild = currentChild.getFollowingSibling();
               }
               if (currentChild==null) {
                  difference = true;
                  logChild(false,e);
                  flushLevel = 1;
               } else {
                  Element leftElement = (Element)currentChild;
                  if (leftElement.getName().equals(e.getName())) {

                     compareElements(leftElement,e);

                     currentChild = currentChild.getFollowingSibling();
                     
                     current = leftElement;
                     childrenContext.add(currentChild);
                     currentChild = current.get(0);
                  } else {
                     difference = true;
                     logChild(false,e);
                     flushLevel = 1;
                  }
               }
            } else {
               difference = true;
               logChild(false,e);
               flushLevel = 1;
            }
            
         }
            break;
            // TODO: handle the case where Characters is received in parts
         case CharactersItem:
         case CommentItem:
         case ProcessingInstructionItem:
            while (currentChild!=null && currentChild.getType()!=item.getType()) {
               if (ignoreWhitespace && currentChild.getType()==Item.ItemType.CharactersItem) {
                  Characters cchild = (Characters)currentChild;
                  if (!allWhitespace(cchild.getText())) {
                     logChild(true,currentChild);
                     difference = true;
                  }
               } else {
                  logChild(true,currentChild);
                  difference = true;
               }
               currentChild = currentChild.getFollowingSibling();
            }
            if (currentChild==null) {
               difference = true;
               logChild(false,(Child)item);
            } else {
               Text currText = (Text)currentChild;
               if (!currText.getNormalizedText().equals(((Text)item).getNormalizedText())) {
                  difference = true;
                  logMessage(false,item,"Text value does not match.");
               }

               if (item.getType()==Item.ItemType.ProcessingInstructionItem) {
                  ProcessingInstruction currPI = (ProcessingInstruction)currentChild;
                  ProcessingInstruction otherPI = (ProcessingInstruction)item;
                  if (!currPI.getName().equals(otherPI.getName())) {
                     difference = true;
                     logMessage(false,item,"PI target "+otherPI.getName()+" does not match.");
                  }
               }

               currentChild = currentChild.getFollowingSibling();

            }
         break;
      }
   }
   
   boolean allWhitespace(String value) {
      int len = value.length();
      for (int i=0; i<len; i++) {
         if (!Character.isWhitespace(value.charAt(i))) {
            return false;
         }
      }
      return true;
   }
   
   void logMessage(boolean left,Item location,String msg) {
      out.print(left ? '<' : '>');
      out.print(Source.formatLocation(location));
      out.println(msg);
   }
   
   void logSiblings(boolean left,Child child) {
      do {
         if (ignoreWhitespace && child.getType()==Item.ItemType.CharactersItem) {
            Characters cchild = (Characters)child;
            if (!allWhitespace(cchild.getText())) {
               logChild(left,child);
               difference = true;
            }
         } else {
            logChild(left,child);
            difference = true;
         }
         child = child.getFollowingSibling();
      } while (child!=null);
   }
   
   void logChild(boolean left, Child child) {
      out.print(left ? '<' : '>');
      out.print(Source.formatLocation(child));
      switch (child.getType()) {
         case ElementItem:
            out.println(" Element "+((Element)child).getName()+" not found.");
            break;
         case CharactersItem:
            out.println(" Extra characters.");
            break;
         case CommentItem:
            out.println(" Extra comment.");
            break;
         case ProcessingInstructionItem:
            out.println(" Extra processing instruction.");
            break;
      }
   }
   
   void compareElements(Element left, Element right) {
      for (Attribute leftAtt : left.getAttributes().values()) {
         Attribute rightAtt = right.getAttributes().get(leftAtt.getName());
         if (rightAtt==null) {
            logMessage(true,left,"Attribute "+leftAtt.getName()+" only here.");
            difference = true;
         } else if (!leftAtt.getNormalizedText().equals(rightAtt.getNormalizedText())) {
            logMessage(false,right,"Attribute "+rightAtt.getName()+" has a different value.");
            difference = true;
         }
      }
      for (Attribute rightAtt : right.getAttributes().values()) {
         Attribute leftAtt = left.getAttributes().get(rightAtt.getName());
         if (leftAtt==null) {
            logMessage(false,right,"Extra attribute "+rightAtt.getName());
            difference = true;
         }
      }
   }
   
}
